package boot

import (
	"flag"
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/gookit/goutil/fsutil"
	"github.com/patrickmn/go-cache"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gorm.io/driver/mysql"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"store/global"
	"store/utils/common"
	"store/utils/zaplog"
	"time"
)

func AppInit() {
	InitConfig()
	InitPathInfo()
	initZap()        //日志组件
	InitLocalCache() //初始化本地缓存
	//InitDB()         //初始化DB列表
	InitSQLite()
}

// InitConfig 使用viper处理应用目录下的config.yaml
func InitConfig() {
	file := flag.String("f", "config.yaml", "the config file")
	flag.Parse()

	viper.SetConfigFile(*file)
	viper.AddConfigPath(".")
	if err := viper.ReadInConfig(); err != nil {
		log.Fatalln("加载应用配置文件时出错:", err)
	}

	// 这个操作会把yaml中的值写到config中的各字段去
	if err := viper.Unmarshal(&global.Config); err != nil {
		log.Fatalln("解析配置文件config时出错:", err)
	}

	viper.WatchConfig()
	viper.OnConfigChange(
		func(in fsnotify.Event) {
			if err := viper.Unmarshal(&global.Config); err != nil {
				log.Println("配置文件被修改，但解析配置文件时，config配置出错：", err)
			} else {
				log.Println("重新加载配置文件")
			}
		},
	)
}

// InitPathInfo 定位根目录并判断是否有上传目录
func InitPathInfo() {
	_, filename, _, _ := runtime.Caller(1)
	currentPath := filepath.Dir(filename)
	// 就是往回跳三层目录，到 store 目录
	global.Config.PathInfo.Root = filepath.Dir(filepath.Dir(currentPath))
	//以下代码创建上传目录
	if global.Config.App.OssType == "local" {
		if !fsutil.IsDir(global.Config.App.UploadDir) {
			if err := os.MkdirAll(global.Config.App.UploadDir, 0755); err != nil {
				log.Fatalln("创建上传目录失败:", err.Error())
			}
		}
	}
}

func initZap() {
	logPath := global.Config.App.LogPath
	if !fsutil.PathExist(logPath) { // 判断是否存在 日志 文件夹
		fmt.Printf("创建 %s 日志目录\n", logPath)
		_ = os.Mkdir(logPath, 0755) // os.ModePerm
	}

	cores := zaplog.Zap.GetZapCores()
	loggerObj := zap.New(zapcore.NewTee(cores...))
	global.Logger = loggerObj.WithOptions(zap.AddCaller())
}

// InitLocalCache 本地缓存的初始化
func InitLocalCache() {
	global.LocalCache = cache.New(5*time.Minute, 10*time.Minute)
}

func InitSQLite() {
	db, err := gorm.Open(sqlite.Open("db/storeTT.db"), &gorm.Config{})
	if err != nil {
		panic("数据库连接失败")
	}

	// 获取底层 *sql.DB 对象
	sqlDB, _ := db.DB()

	// 连接池参数配置
	sqlDB.SetMaxIdleConns(5)                                                                      // 空闲连接数（建议 1-5）
	sqlDB.SetMaxOpenConns(1)                                                                      // SQLite 最大打开连接建议设置为 1:ml-citation{ref="3" data="citationList"}
	sqlDB.SetConnMaxLifetime(time.Duration(global.Config.DBConfig.ConnMaxLifeTime) * time.Second) // 连接复用时间

	// 简单触发表存在检查 检查 user 表是否存在
	exist := db.Migrator().HasTable("user")
	if exist {
		fmt.Println("数据库连接成功！可以继续...")
	} else {
		fmt.Println("user 表不存在")
	}

	// 将 *gorm.DB 指针赋值给全局变量
	global.DB = db
}

func InitDB() {
	var loggerMode logger.Interface
	if common.IsDevEnv() {
		loggerMode = logger.Default.LogMode(logger.Info)
	} else {
		loggerMode = logger.Default.LogMode(logger.Silent)
	}

	//MySQL 连接配置
	dsn := global.Config.DBConfig.Dsn
	// 使用 gorm.Open 初始化连接
	// 可以简单：db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	db, err := gorm.Open(
		mysql.New(
			mysql.Config{
				DSN:                       dsn,   // DSN data source name
				DefaultStringSize:         191,   // string 类型字段的默认长度
				DisableDatetimePrecision:  true,  // 禁用 datetime 精度，MySQL 5.6 之前的数据库不支持
				DontSupportRenameIndex:    true,  // 重命名索引时采用删除并新建的方式，MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
				DontSupportRenameColumn:   true,  // 用 `change` 重命名列，MySQL 8 之前的数据库和 MariaDB 不支持重命名列
				SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
			},
		), &gorm.Config{Logger: loggerMode},
	)
	if err != nil {
		panic("数据库连接失败: " + err.Error())
	}

	// 可选：配置连接池
	sqlDB, _ := db.DB()
	sqlDB.SetMaxIdleConns(global.Config.DBConfig.MaxIdleConn)
	sqlDB.SetMaxOpenConns(global.Config.DBConfig.MaxOpenConn)
	sqlDB.SetConnMaxLifetime(time.Duration(global.Config.DBConfig.ConnMaxLifeTime) * time.Second)
	sqlDB.SetConnMaxIdleTime(time.Duration(global.Config.DBConfig.ConnMaxIdleTime) * time.Second)

	// 将 *gorm.DB 指针赋值给全局变量
	global.DB = db
}
